package com.vpen.client.matter;

import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;

import java.io.IOException;
import java.util.concurrent.*;

/**
 * 与服务器连接，当有新的事项提交的时候，服务器通知客户端,客户端把事项存到自己的数据库
 * 单例
 *
 * @author 韦鹏
 * @date 2022/11/25 10:02
 */
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class MatterClient {
    private WebSocketSession session;

    private final StandardWebSocketClient standardWebSocketClient;
    private final MatterHandler handler;

    private final TaskScheduler taskScheduler;

    @Value("${matter.url}")
    private String url;

    @Value("${matter.key}")
    private String key;

    /**
     * ping的间隔时间
     */
    private static final Integer PingTime = 30_000;
    /**
     * 重连的时间
     */
    private static final Integer ReconnectTime = 5_000;


    /**
     * 连接方法
     */
    public WebSocketSession connect() throws ExecutionException, InterruptedException, TimeoutException {
        return connect(200);
    }

    public WebSocketSession connect(long timeout) throws ExecutionException, InterruptedException, TimeoutException {
        ListenableFuture<WebSocketSession> future = standardWebSocketClient.doHandshake(handler, getUri());
        // 阻塞取
        log.debug("建立连接");
        this.session = future.get(timeout, TimeUnit.MILLISECONDS);
        return this.session;
    }

    /**
     * 重连方法
     */
    public WebSocketSession reconnect() {
        CompletableFuture.runAsync(() -> {
            session = null;
            while (session == null) {
                try {
                    log.debug("重连中.....");
                    session = connect(ReconnectTime);
                } catch (InterruptedException | ExecutionException | TimeoutException ignored) {

                }
            }
            log.debug("重连成功,{}", session);
        });
        return session;
    }

    /**
     * 断开连接
     **/
    public void disconnect() {
        try {
            if (null != this.session) {
                this.session.close(CloseStatus.NORMAL);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 心跳检测，每隔60秒ping服务器，
     */
    public void ping() {
        taskScheduler.scheduleAtFixedRate(() -> {
            try {
                if (this.session != null) {
                    log.debug("ping");
                    this.session.sendMessage(new PingMessage());
                }
            } catch (IOException e) {
                log.error("服务不可达");
                throw new RuntimeException(e);
            }
        }, PingTime);

    }


    /**
     * 发送消息
     */
    public void send(Object message) {
        try {
            getSession().sendMessage(new TextMessage(JSONUtil.toJsonStr(message)));
        } catch (IOException e) {
            log.error("发送失败");
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取session
     */
    public WebSocketSession getSession() {
        // 如果为空，重连
        if (null == this.session) {
            try {
                connect();
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                log.error("发送失败");
                throw new RuntimeException(e);
            }
        }
        return this.session;
    }


    private String getUri() {
        return this.url + "/" + this.key;
    }

}
